// ewave_lib.cpp
//
/*
 *  Implementation Of Glen Neely-Mastering Elliot Wave 
 * 
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *  
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*!
	\file   ewave_lib.cpp
	\brief  Elliot Wave Engine 
	\author Ray Rope <sendyourdollars@hotmail.com>, Johan S?ensen <johanps@users.sourceforge.net>
*/

#include <stdafx.h>
#include <stdio.h>
#include <math.h>
#include <float.h>
#include <assert.h>
#include "ewave_lib.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


const float phi = 0.618033989f;
const float phi_precision = phi * 0.02f;


/////////////////////////////////////////////////////////////////////////////


WaveVector::size_type EW_BuildMonoWaves(const PriceNodeVec& pricenodes, WaveVector& monowaves)
{
  const int num_nodes = pricenodes.size();
  int start = 0;
  int in_index = 1;
  float direction;        // >0 means up, <0 means down
  float next_dir;

  if (num_nodes < 2)
    return 0;

  // the first monowave starts at our first data point
  direction = (pricenodes[1].mPrice - pricenodes[0].mPrice);

  while (in_index < num_nodes)
  {
    // find out where the current monowave ends
    while (in_index < num_nodes - 1)
    {
      next_dir = (pricenodes[in_index+1].mPrice - pricenodes[in_index].mPrice);
      if (direction * next_dir < 0.0f)
        break;
      in_index++;
    }
    
    monowaves.push_back(WaveNode(&pricenodes, start, in_index));
    direction = next_dir;
    start = in_index++;
  } // while

  return monowaves.size();
}


/////////////////////////////////////////////////////////////////////////////

static int longest_m0_index = -1;
static int longest_m0_size = 1;
static int longest_m2_index = -1;
static int longest_m2_size = 1;


// Given "M1", find the monowave, or wave group, denoted M2
// note that when the resulting M2 is a group, it will encompass three or more waves in the input vector
bool EW_FindM2(const PolyWaveVec& waves, const WaveNode& M1, int m1, WaveNode& M2)
{
  bool bFound = false;
  float m1start = M1.StartPrice();
  float m1end   = M1.EndPrice();
  float m1high  = max(m1start, m1end);
  float m1low   = min(m1start, m1end);
  float m2high = m1low;
  float m2low  = m1high;
  int extreme_low_index = -1;
  int extreme_high_index = -1;

  // note that the m1 input-parameter may be an in-exact hint as to where M1 ends,
  // we have to find the mono wave that corresponds to the wave (or group) that corresponds to M1 for the moment
  for (; m1 < waves.size(); m1++)
    if (waves[m1].mEndIndex == M1.mEndIndex)
      break;

  int i;
  for (i = m1 + 1; i < waves.size(); i++)
  {
    if (waves[i].EndPrice() > m1high || waves[i].EndPrice() < m1low)
    {
      bFound = true;
      break;
    }
    if (waves[i].EndPrice() > m2high)
      m2high = waves[i].EndPrice(), extreme_high_index = i;
    if (waves[i].EndPrice() < m2low)
      m2low = waves[i].EndPrice(), extreme_low_index = i;
  } // while

  int n = i - m1;
  if (n > 2)
  {
    // if it took more than two monowaves to get out the the m1 range,
    // then there was a turn somewhere, and the end of m2 is at the "extreme" index
    // and m2 must always consist of an odd number of monowaves
    if (((extreme_low_index - m1) %2) != 0)
      i = extreme_low_index;
    else
    if (((extreme_high_index - m1) %2) != 0)
      i = extreme_high_index;
    else
    {
      TRACE("EW_FindM2: problem finding m2 for m1 ending at %d\n", M1.EndDate());
    }
  }
  else
  if (n == 2)
  {
    i = m1 + 1;       // m2 is confirmed complete somewhere within m1, since m3 exceeds m1 high/low
  }
  // else only remaining alternative is that m2 retraced m1 completely itself, thus i = m1 + 1

  // just for fun, keep track of where we found the most intricate m2
  n = i - m1;
  if (n > longest_m2_size)
  {
    longest_m2_size = n;
    longest_m2_index = m1;
  }
  
  // if bFound is false, then the returned M2 is only partially completed
  M2.mpPriceNodeVec = &waves.m_PriceNodes;
  M2.mStartIndex = M1.mEndIndex;
  if (i < waves.size())
    M2.mEndIndex = waves[i].mEndIndex;
  else
    M2.mEndIndex = waves.m_PriceNodes.size();
  return bFound;
}

// Given "m1", find the monowave, or wave group, denoted M0
// note that when the resulting M0 is a group, it will encompass three or more waves in the input vector
bool EW_FindM0(const PolyWaveVec& waves, const WaveNode& M1, int m1, WaveNode& M0)
{
  bool bFound = false;
  float m1start = M1.StartPrice();
  float m1end   = M1.EndPrice();
  float m1high  = max(m1start, m1end);
  float m1low   = min(m1start, m1end);
  float m0high = m1low;
  float m0low  = m1high;
  int extreme_low_index = -1;
  int extreme_high_index = -1;

  // note that the m1 input-parameter may be an in-exact hint as to where M1 begins,
  // we have to find the mono wave that corresponds to the wave (or group) that corresponds to M1 for the moment
  for (; m1 > 0; m1--)
    if (waves[m1].mStartIndex == M1.mStartIndex)
      break;

  int i;
  for (i = m1 - 1; i >= 0; i--)
  {
    if (waves[i].StartPrice() > m1high || waves[i].StartPrice() < m1low)
    {
      bFound = true;
      break;
    }
    if (waves[i].StartPrice() > m0high)
      m0high = waves[i].StartPrice(), extreme_high_index = i;
    if (waves[i].StartPrice() < m0low)
      m0low = waves[i].StartPrice(), extreme_low_index = i;
  } // while

  int n = m1 - i;
  if (n > 2)
  {
    // if it took more than two monowaves to get out the the m1 range,
    // then there was a turn somewhere, and the start of m0 is at the "extreme" index
    // and m0 must always consist of an odd number of monowaves
    if (((m1 - extreme_low_index) %2) != 0)
      i = extreme_low_index;
    else
    if (((m1 - extreme_high_index) %2) != 0)
      i = extreme_high_index;
    else
    {
      TRACE("EW_FindM0: problem finding m0 for m1 starting at %d\n", M1.StartDate());
    }
  }
  else
  if (n == 2)
  {
    i = m1 - 1;       // m0 is confirmed complete somewhere within m1, since m-1 exceeds m1 high/low
  }
  // else only remaining alternative is that m0 retraced m1 completely itself, thus i = m1 - 1

  // just for fun, keep track of where we found the most intricate m2
  n = m1 - i;
  if (n > longest_m0_size)
  {
    longest_m0_size = n;
    longest_m0_index = m1;
  }

  M0.mpPriceNodeVec = &waves.m_PriceNodes;
  M0.mEndIndex = M1.mStartIndex;
  if (i >= 0)
    M0.mStartIndex = waves[i].mStartIndex;
  else
    M0.mStartIndex = -1;
  return bFound;
}


//
// Look at each individual monowave by itself (mentally consider it "m1"), and determine its
// relationship with neighbouring monowaves (or monowave groups) "m0" and "m2" to determine 
// which Retracement Rule should be applied
//
void EW_ProcessRulesOfRetracement(PolyWaveVec& waves)
{
  WaveNode M0(&waves.m_PriceNodes, 0, 0);
  WaveNode M2(&waves.m_PriceNodes, 0, 0);
  WaveNode M3(&waves.m_PriceNodes, 0, 0);
  float m3retracement;

  if (waves.size() < 2)
    return;

  longest_m0_index = -1;
  longest_m0_size = 1;
  longest_m2_index = -1;
  longest_m2_size = 1;

  for (int m1 = 0; m1 < waves.size(); m1++)
  {
    if (EW_FindM0(waves, waves[m1], m1, M0))
      waves[m1].m0StartIndex = M0.mStartIndex;
    if (EW_FindM2(waves, waves[m1], m1, M2))
      waves[m1].m2EndIndex = M2.mEndIndex;

    waves[m1].mM0Retracement = 0.0f;
    waves[m1].mM2Retracement = 0.0f;

    if (M2.mEndIndex < waves.m_PriceNodes.size())
    {
      waves[m1].mM2Retracement = fabs(M2.LengthInPrice() / waves[m1].LengthInPrice());
      if (waves[m1].mM2Retracement < (1.0f - phi))
        waves[m1].mRetracementRule = 1;
      else
      if (waves[m1].mM2Retracement < (phi - phi_precision))
        waves[m1].mRetracementRule = 2;
      else
      if (waves[m1].mM2Retracement < (phi + phi_precision))
        waves[m1].mRetracementRule = 3;
      else
      if (waves[m1].mM2Retracement < 1.0f)
        waves[m1].mRetracementRule = 4;
      else
      if (waves[m1].mM2Retracement < (1.0f + phi))
        waves[m1].mRetracementRule = 5;
      else
      if (waves[m1].mM2Retracement <= (2.0f + phi))
        waves[m1].mRetracementRule = 6;
      else
        waves[m1].mRetracementRule = 7;
    }

    if (M0.mStartIndex >= 0)
    {
      waves[m1].mM0Retracement = fabs(M0.LengthInPrice() / waves[m1].LengthInPrice());
      switch (waves[m1].mRetracementRule)
      {
        case 1:
          if (waves[m1].mM0Retracement < phi)
            waves[m1].mCondition = 'a';
          else
          if (waves[m1].mM0Retracement < 1.0f)
            waves[m1].mCondition = 'b';
          else
          if (waves[m1].mM0Retracement < (1.0f + phi))
            waves[m1].mCondition = 'c';
          else
            waves[m1].mCondition = 'd';
          break;

        case 2:
          if (waves[m1].mM0Retracement < (1.0f - phi))
            waves[m1].mCondition = 'a';
          else
          if (waves[m1].mM0Retracement < phi)
            waves[m1].mCondition = 'b';
          else
          if (waves[m1].mM0Retracement < 1.0f)
            waves[m1].mCondition = 'c';
          else
          if (waves[m1].mM0Retracement <= (1.0f + phi))
            waves[m1].mCondition = 'd';
          else
            waves[m1].mCondition = 'e';
          break;

        case 3:
          if (waves[m1].mM0Retracement < (1.0f - phi))
            waves[m1].mCondition = 'a';
          else
          if (waves[m1].mM0Retracement < phi)
            waves[m1].mCondition = 'b';
          else
          if (waves[m1].mM0Retracement < 1.0f)
            waves[m1].mCondition = 'c';
          else
          if (waves[m1].mM0Retracement < (1.0f + phi))
            waves[m1].mCondition = 'd';
          else
          if (waves[m1].mM0Retracement <= (2.0f + phi))
            waves[m1].mCondition = 'e';
          else
            waves[m1].mCondition = 'f';
          break;

        case 4:
          if (waves[m1].mM0Retracement < (1.0f - phi))
            waves[m1].mCondition = 'a';
          else
          if (waves[m1].mM0Retracement < 1.0f)
            waves[m1].mCondition = 'b';
          else
          if (waves[m1].mM0Retracement < (1.0f + phi))
            waves[m1].mCondition = 'c';
          else
          if (waves[m1].mM0Retracement <= (2.0f + phi))
            waves[m1].mCondition = 'd';
          else
            waves[m1].mCondition = 'e';
          // figure out category depending on M3!
          m3retracement = 0.0f;
          if (EW_FindM2(waves, M2, m1, M3))
            m3retracement = fabs(M3.LengthInPrice() / M2.LengthInPrice());
          if (m3retracement >= 1.0f && m3retracement < (1.0f + phi))
            waves[m1].mCategory = 1;
          else
          if (m3retracement >= (1.0f + phi) && m3retracement <= (2.0f + phi))
            waves[m1].mCategory = 2;
          else
          if (m3retracement > (2.0f + phi))
            waves[m1].mCategory = 3;
          break;

        case 5:
          if (waves[m1].mM0Retracement < 1.0f)
            waves[m1].mCondition = 'a';
          else
          if (waves[m1].mM0Retracement < (1.0f + phi))
            waves[m1].mCondition = 'b';
          else
          if (waves[m1].mM0Retracement <= (2.0f + phi))
            waves[m1].mCondition = 'c';
          else
            waves[m1].mCondition = 'd';
          break;

        case 6:
          if (waves[m1].mM0Retracement < 1.0f)
            waves[m1].mCondition = 'a';
          else
          if (waves[m1].mM0Retracement < (1.0f + phi))
            waves[m1].mCondition = 'b';
          else
          if (waves[m1].mM0Retracement <= (2.0f + phi))
            waves[m1].mCondition = 'c';
          else
            waves[m1].mCondition = 'd';
          break;

        case 7:
          if (waves[m1].mM0Retracement < 1.0f)
            waves[m1].mCondition = 'a';
          else
          if (waves[m1].mM0Retracement < (1.0f + phi))
            waves[m1].mCondition = 'b';
          else
          if (waves[m1].mM0Retracement <= (2.0f + phi))
            waves[m1].mCondition = 'c';
          else
            waves[m1].mCondition = 'd';
          break;
        // default: don't care
      } // switch
    }
  } // for

  if (longest_m0_index > 0)
    TRACE("longest m0 at %s : %d components\n", waves[longest_m0_index].StartDate().Format(), longest_m0_size);
  if (longest_m2_index > 0)
    TRACE("longest m2 at %s : %d components\n", waves[longest_m2_index].EndDate().Format(), longest_m2_size);
}


/////////////////////////////////////////////////////////////////////////////

static int ReportNewPeriod(const ETime& highDate, 
                           const ETime& lowDate, 
                                 int valuesPerPeriod,
                                 PriceNode& tempNode,     // in-out 
                                 PriceNodeVec& outVec)
{
  int newOutNodes = 0;

  if (tempNode.mHigh == FLT_MIN)
    tempNode.mHigh = tempNode.mLast;
  if (tempNode.mLow == FLT_MAX)
    tempNode.mLow = tempNode.mLast;

  if (valuesPerPeriod != 2)
  { // report one value per period, either mean of high-low, or close
    if (valuesPerPeriod == 1)
      tempNode.mPrice = (tempNode.mLow + tempNode.mHigh) / 2.0f;
    else
      tempNode.mPrice = tempNode.mLast;
    outVec.push_back(tempNode);
    newOutNodes++;
  }
  else
  { // report two values (high and low) in the order they occur within the period
    if (highDate == lowDate)
    {
      tempNode.mPrice = (tempNode.mLow + tempNode.mHigh) / 2.0f;
      outVec.push_back(tempNode);
      newOutNodes++;
      TRACE("Ambiguous high/low at %s\n", tempNode.mDate.Format());
    }
    else
    {
      PriceNode tempNode2 = tempNode;
      tempNode.mDate = lowDate;
      tempNode.mPrice = tempNode.mOpen = tempNode.mHigh = tempNode.mLast = tempNode.mLow;
      tempNode.mVolume /= 2.0f;
      tempNode2.mDate = highDate;
      tempNode2.mPrice = tempNode2.mOpen = tempNode2.mLow = tempNode2.mLast = tempNode2.mHigh;
      tempNode2.mVolume /= 2.0f;
      if (highDate < lowDate)
      {
        outVec.push_back(tempNode2);
        outVec.push_back(tempNode);
      }
      else
      {
        outVec.push_back(tempNode);
        outVec.push_back(tempNode2);
      }
      newOutNodes += 2;
    }
  } // else, two values per period

  tempNode.Clear();
  tempNode.mHigh = FLT_MIN;
  tempNode.mLow = FLT_MAX;
  return newOutNodes;
}

int EW_MergeDataIntoPeriods(const PriceNodeVec& inVec, int interval, int valuesPerPeriod, PriceNodeVec& outVec, std::list<int>* pAmbiguities)
{
  const int inNodes = inVec.size();
  int newOutNodes = 0;
  PriceNodeVec::const_iterator inIt = inVec.begin();
  PriceNodeVec::iterator outIt = outVec.begin();
  ETime oldDate = 0;
  ETime date, highDate, lowDate;
  PriceNode tempNode;

  tempNode.Clear();
  tempNode.mHigh = FLT_MIN;
  tempNode.mLow = FLT_MAX;

  for (; inIt != inVec.end(); inIt++)
  {
    const PriceNode& inNode = (*inIt);
    date = inNode.mDate;
    if (oldDate == 0)
      oldDate = date;

    bool bNewPeriod = false;
    if (interval < DAYSECONDS)
      bNewPeriod = (oldDate.GetSecondsWithinDay() / interval) != (date.GetSecondsWithinDay() / interval); // consolidate to intraday periods (e.g. hour)
    else
    if (interval == DAYSECONDS)
      bNewPeriod = (oldDate.GetDay() != date.GetDay());             // consolidate to days
    else
    if (interval == WEEKSECONDS)
      bNewPeriod = (date.GetDayOfWeek() < oldDate.GetDayOfWeek());  // consolidate to weeks
    else
    // if (interval == MONTHSECONDS)
      bNewPeriod = (oldDate.GetMonth() != date.GetMonth());         // consolidate to months

    if (bNewPeriod)
    {
      tempNode.mDate = oldDate;
      int n = ReportNewPeriod(highDate, lowDate, valuesPerPeriod, tempNode, outVec);
      newOutNodes += n;
      if (n < 2 && valuesPerPeriod == 2 && pAmbiguities)
        pAmbiguities->push_back(newOutNodes);
    } // if bNewPeriod

    //update period high low last and volume
    if ((inNode.mHigh > tempNode.mHigh) && (inNode.mHigh > 0.0f))
      tempNode.mHigh = inNode.mHigh, highDate = date;
    else
    if ((inNode.mLast > tempNode.mHigh) && (inNode.mLast > 0.0f))
      tempNode.mHigh = inNode.mLast, highDate = date;

    if ((inNode.mLow < tempNode.mLow) && (inNode.mLow > 0.0f))
      tempNode.mLow = inNode.mLow, lowDate = date;
    else
    if ((inNode.mLast < tempNode.mLow) && (inNode.mLast > 0.0f))
      tempNode.mLow = inNode.mLast, lowDate = date;

    if (inNode.mLast > 0.0f)
      tempNode.mLast = inNode.mLast;
    tempNode.mVolume += inNode.mVolume;

    if (tempNode.mOpen == 0.0f)
    {
      if (inNode.mOpen != 0.0f)
        tempNode.mOpen = inNode.mOpen;
      else
      // this is a bit wrong, but if there's no open values in the indata, this is better than nothing; better the more fine-grained the indata is
      if (inNode.mLast != 0.0f)
        tempNode.mOpen = inNode.mLast;
    }
    oldDate = date;
  } // while

  // was there an unfinished period?
  if (tempNode.mLast > 0.0f)
  {
    tempNode.mDate = oldDate;
    int n = ReportNewPeriod(highDate, lowDate, valuesPerPeriod, tempNode, outVec);
    newOutNodes += n;
    if (n < 2 && valuesPerPeriod == 2 && pAmbiguities)
      pAmbiguities->push_back(newOutNodes);
  }

  return newOutNodes;
}


int EW_GetAveragePeriod(const PriceNodeVec& vec)
{
  if (vec.size() < 2)
    return 0;

  double sum = 0.0;
  PriceNodeVec::const_iterator it = vec.begin();
  ETime t = (*it++).mDate;
  for (; it != vec.end(); it++)
  {
    sum += (*it).mDate.Diff(t, 1, WEEKDAY_ONLY);
    t = (*it).mDate;
  }

  return (int)(sum / (vec.size() - 1));
}


/////////////////////////////////////////////////////////////////////////////
// Experimental procedures

static tIndexAndLength
EW_FindSimilarWaveSequence(const PolyWaveVec& waves, int m1, int& startIndex, int lengthThreshold, float qmax)
{
  PolyWaveVec m1cache(waves.m_PriceNodes);    // this stores M1, M0, M-1, M-2 etc (in reverse order)
  m1cache.reserve(20);
  int len;
  float qmin = 1.0f / qmax;

  // populate the cache containing the M0, M-1, M-2 etc sequence we're looking for
  WaveNode M1 = waves[m1];
  m1cache.push_back(waves[m1]);
  WaveNode& M1ref = m1cache[0];
  for (int i=0; i<10; i++)
  {
    WaveNode M0;
    if (EW_FindM0(waves, M1, m1, M0))
    {
      M1ref.mM0Retracement = fabs(M0.LengthInPrice() / M1ref.LengthInPrice());
      m1cache.push_back(M0);
      M1ref = m1cache[ m1cache.size()-1 ];
    }
    else
      break;
    M1 = M0;
  }

  do
  {
    if (startIndex == m1)
      continue;
    if (waves[startIndex].LengthInPrice() * waves[m1].LengthInPrice() < 0.0f)
      continue;   // not looking at a wave in the same direction

    float q = 9999.0f;
    len = 0;
    WaveNode Mc1 = waves[startIndex];
    WaveNode Mc0;
    if (EW_FindM0(waves, Mc1, startIndex, Mc0))
    {
      Mc1.mM0Retracement = fabs(Mc0.LengthInPrice() / Mc1.LengthInPrice());
      q = fabs(log10(Mc1.mM0Retracement) - log10(m1cache[len].mM0Retracement));
    }
    else break;

    while ((startIndex-len) >= 0 && (q <= qmax))
//    while ((startIndex-len) >= 0 && waves[startIndex-len].mRetracementRule == waves[m1-len].mRetracementRule)
    {
      len++;
      Mc1 = Mc0;
      if (EW_FindM0(waves, Mc1, startIndex, Mc0)) // it's ok to supply the "wrong" startindex; it's just used as searching start anyway
      {
        Mc1.mM0Retracement = fabs(Mc0.LengthInPrice() / Mc1.LengthInPrice());
        q = fabs(log10(Mc1.mM0Retracement) - log10(m1cache[len].mM0Retracement));
      }
      else
        break;
    }

    if (len >= lengthThreshold)
    {
      startIndex -= len;
      TRACE("EW_FindMatchingWaveSequence: found match for m1=%d, len %d at %d\n", m1, len, startIndex);
      break;
    }
  } while (--startIndex > 0);

  return tIndexAndLength(startIndex, len);
}


int EW_FindSimilarWaveSequences(const PolyWaveVec& waves, int m1, tNodePairSet& matches)
{
  int startIndex = waves.size() - 1;
  int lengthThreshold = 3;
  float qmax = 0.2f; // sqrt(2.0);
  while (startIndex > 0)
  {
    tIndexAndLength IndexAndLength = EW_FindSimilarWaveSequence(waves, m1, startIndex, lengthThreshold, qmax);
    startIndex--;
    if (IndexAndLength.second >= lengthThreshold)
    {
      int i1 = waves[IndexAndLength.first].mStartIndex;
      int i2 = waves[IndexAndLength.first + IndexAndLength.second - 1].mEndIndex;
      matches.push_back(tNodePair(&waves.m_PriceNodes[i1], &waves.m_PriceNodes[i1]));
    }
  }
  return matches.size();
}

/////////////////////////////////////////////////////////////////////////////
// find instances of very speedy advances or declines of at least 'sizePercent' 
// and at a speed of 'minSpeed' percent per hour

size_t EW_FindASpeedyChange(const PriceNodeVec& nodes, size_t startIndex, double sizePercent, double minSpeed)
{
  // take the startpoint at startIndex, draw a line y = ax + b 
  double leeway = (minSpeed > 0.0 ? 0.99995 : 1.00005);
  double startValue = nodes[startIndex].mLast * leeway;
  double targetValue = nodes[startIndex].mLast * (1.0 + sizePercent / 100.0);
  double lastValue;
  ETime startDate = nodes[startIndex].mDate;
  minSpeed = startValue * minSpeed / (60.0 * 60.0 * 100.0);    // translate speed to absolute change per second
  size_t z = nodes.size();
  size_t i = startIndex + 1;

  for (; i<z; i++)
  {
    int diffSecs = nodes[i].mDate - startDate;                // nodes[i].date.Diff(startDate, 1, WEEKDAY_ONLY);
    lastValue = nodes[i].mLast;
    if (   (sizePercent > 0.0 && lastValue < startValue + diffSecs * minSpeed)
        || (sizePercent < 0.0 && lastValue > startValue + diffSecs * minSpeed))
      break;
    if ((  (sizePercent > 0.0 && lastValue >= targetValue)
        || (sizePercent < 0.0 && lastValue <= targetValue))
        && diffSecs >= 5*60 && i-startIndex > 3)
      return i - startIndex;
  }
  return 0;
}

int EW_FindSpeedyChanges(const PriceNodeVec& nodes, double sizePercent, double minSpeed, tNodePairSet& matches)
{
  size_t startIndex = 0;

  while (startIndex < nodes.size())
  {
    size_t s = EW_FindASpeedyChange(nodes, startIndex, sizePercent, minSpeed);
    if (s > 1)
    {
      matches.push_back(tNodePair(&nodes[startIndex], &nodes[startIndex+s-1]));
      startIndex += s;
    }
    startIndex++;
  }

  return matches.size();
}

/////////////////////////////////////////////////////////////////////////////
// find instances of very speedy advances or declines
// reaching a speed of 'minSpeed' percent per hour

size_t EW_FindASpeedyChange2(const PriceNodeVec& nodes, size_t startIndex, size_t minTime, double minSpeed)
{
  // take the startpoint at startIndex, draw a line y = ax + b 
  double startValue = nodes[startIndex].mLast;
  double lastClose = 0.0;
  ETime startDate = nodes[startIndex].mDate;
  minSpeed = startValue * minSpeed / (60.0 * 60.0 * 100.0);    // translate speed to absolute change per second
  size_t z = nodes.size();
  size_t i = startIndex + 1;
  int diffSecs = 0;

  for (; i<z  && diffSecs < minTime; i++)
  {
    lastClose = nodes[i].mLast;
    diffSecs = nodes[i].mDate.Diff(startDate, 1, WEEKDAY_ONLY);
    if (   (minSpeed > 0.0 && lastClose < startValue + diffSecs * minSpeed)
        || (minSpeed < 0.0 && lastClose > startValue + diffSecs * minSpeed))
      break;
  }

  if (diffSecs >= minTime && i > startIndex + 2)
  {
    if (   (minSpeed > 0.0 && lastClose >= startValue + diffSecs * minSpeed)
        || (minSpeed < 0.0 && lastClose <= startValue + diffSecs * minSpeed))
    {
      return i - startIndex;
    }
  }
  return 0;
}

int EW_FindExit(const PriceNodeVec& nodes, size_t startIndex, double minSpeed, double stopLoss)
{
  // take the startpoint at startIndex, draw a line y = ax + b 
  double leeway = (minSpeed > 0.0 ? 0.99995 : 1.00005);
  double startValue = nodes[startIndex].mLast * leeway;
  double lastValue;
  ETime startDate = nodes[startIndex].mDate;
  minSpeed = startValue * minSpeed / (60.0 * 60.0 * 100.0);    // translate speed to absolute change per second
  size_t z = nodes.size();
  size_t i = startIndex + 1;

  for (; i<z; i++)
  {
    int diffSecs = nodes[i].mDate.Diff(startDate, 1, WEEKDAY_ONLY);
    lastValue = nodes[i].mLast;
    if (   (minSpeed > 0.0 && (lastValue < startValue + diffSecs * minSpeed))
        || (minSpeed < 0.0 && (lastValue > startValue + diffSecs * minSpeed)))
      break;
  }

  // apply a gliding stop-loss after the speed goes under the minimum
  stopLoss = (minSpeed > 0.0 ? max(stopLoss, lastValue * 0.9995) : min(stopLoss, lastValue * 1.0005));  // raise/lower stoploss?
  for (; i<z; i++)
  {
    lastValue = nodes[i].mLast;
    if (   (minSpeed > 0.0 && (lastValue < stopLoss))
        || (minSpeed < 0.0 && (lastValue > stopLoss)))
      break;
    stopLoss = (minSpeed > 0.0 ? max(stopLoss, lastValue * 0.9995) : min(stopLoss, lastValue * 1.0005));  // raise/lower stoploss?
  }
  return i;
}


int EW_FindSpeedyChanges2(const PriceNodeVec& nodes, size_t minTime, double minSpeed, tNodePairSet& matches)
{
  size_t startIndex = 0;
  double exitSpeed = minSpeed;
  if (minSpeed > 0.5)  exitSpeed = 0.5;
  if (minSpeed < -0.5) exitSpeed = -0.5;

  while (startIndex < nodes.size())
  {
    size_t s = EW_FindASpeedyChange2(nodes, startIndex, minTime, minSpeed);
    if (s > 1)
    {
      if (startIndex + s >= nodes.size())
        s = nodes.size() -1 - startIndex;
      size_t x = EW_FindExit(nodes, startIndex, exitSpeed, nodes[startIndex + s].mLast);
      startIndex += s;
      if (x > startIndex && x < nodes.size())
        matches.push_back(tNodePair(&nodes[startIndex], &nodes[x]));
      startIndex = x;
    }
    startIndex++;
  }

  return matches.size();
}

/////////////////////////////////////////////////////////////////////////////
// find instances local bottoms or tops 
//

int EW_IsLocalExtreme(const PriceNodeVec& nodes, size_t startIndex, size_t lookBackSeconds)
{
  size_t i = startIndex;
  double minVal = (nodes[i].mLow  > 0.0 ? nodes[i].mLow  : nodes[i].mLast);
  double maxVal = (nodes[i].mHigh > 0.0 ? nodes[i].mHigh : nodes[i].mLast);
  ETime startDate = nodes[i].mDate;
  bool bIsLow = true;
  bool bIsHigh = true;
  if (startIndex < 2)
    return 0;

  for (--i; i > 0; i--)
  {
    double low  = (nodes[i].mLow  > 0.0 ? nodes[i].mLow: nodes[i].mLast);
    double high = (nodes[i].mHigh > 0.0 ? nodes[i].mHigh: nodes[i].mLast);
    if (bIsLow && low < minVal)
    {
      bIsLow = false;
      if (!bIsHigh)
        return 0;
    }
    else
    if (bIsHigh && nodes[i].mLast > maxVal)
    {
      bIsHigh = false;
      if (!bIsLow)
        return 0;
    }

    int diffSecs = startDate.Diff(nodes[i].mDate, 1, WEEKDAY_ONLY);
    if (diffSecs >= lookBackSeconds)
      break;
  } // for

  if (bIsHigh)
    return 1;
  if (bIsLow)
    return -1;
  return 0;
}


int EW_FindLocalExtremes(const PriceNodeVec& nodes, size_t lookBackMinutes, size_t lookAheadMinutes, tNodeSet& matches)
{
  size_t prevExtremeIndex = 0;
  int prevExtremeDirection = 0;
  size_t startIndex = 1;

  while (startIndex < nodes.size())
  {
    int s = EW_IsLocalExtreme(nodes, startIndex, lookBackMinutes * 60);
    if (s != 0)
    {
      int diff = nodes[startIndex].mDate.Diff(nodes[prevExtremeIndex].mDate, 1, WEEKDAY_ONLY);
      if (prevExtremeIndex && (s * prevExtremeDirection > 0) && diff < lookAheadMinutes*60)
        matches.back() = &nodes[startIndex];
      else
        matches.push_back(&nodes[startIndex]);
      prevExtremeIndex = startIndex;
      prevExtremeDirection = s;
    }
    startIndex++;
  }

  return matches.size();
}
